package gjj.gui;

import gjj.algorithm.image.ImageSplitter;
import gjj.util.ImageUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDStream;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class PdfImageSplitPreviewer extends OpenCVSupportedGui {

    private int offSet = 50;

    private JLabel imageLabel;

    private JTextField pageNo;

    private JTextField imageNo;

    private File selectedFile;

    private void showImageNewWindow(Mat img, String winTitle) {
        Mat dst = new Mat();
        ImageUtils.fitImgSize(img, dst, IMAGE_WIDTH, IMAGE_HEIGHT);
        MatOfByte matOfByte = new MatOfByte();
        Imgcodecs.imencode(".jpeg", dst, matOfByte);
        JLabel imageComponent = new JLabel(new ImageIcon(matOfByte.toArray()), SwingConstants.CENTER);
        JFrame mainFrame = new JFrame(winTitle);
        mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        mainFrame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
        mainFrame.setLocation(offSet, offSet);
        mainFrame.add(imageComponent);
        mainFrame.setVisible(true);
        offSet += 50;
    }

    private Mat splitAndDraw(Mat src) {
        //计算切图结果
        ImageSplitter.ImageSplitResult findSmallRegionResult = ImageSplitter.findSmallImageRegions(src);
        List<MatOfPoint> contours = findSmallRegionResult.getContours();
        Mat fillSmallRegionResult = findSmallRegionResult.getFillSmallRegionResult();
        showImageNewWindow(fillSmallRegionResult, "填充过小区域后的结果");
        Mat fillIrregularRegionResult = findSmallRegionResult.getFillIrregularRegionResult();
        showImageNewWindow(fillIrregularRegionResult, "填充不规则区域后的结果");
        //切图结果是四边加了白边的图像所得，所以原图上也加上白边
        Core.copyMakeBorder(src, src, 10, 10, 10, 10, Core.BORDER_CONSTANT, new Scalar(255, 255, 255));
        Mat outputImg = new Mat();
        src.copyTo(outputImg);
        ImageUtils.adaptImgChannel(outputImg);
        for (int i = 0; i < contours.size(); ++i) {
            MatOfPoint contour = contours.get(i);
            Rect rect = Imgproc.boundingRect(contour);
            //轮廓用绿色线条展示出来
            Imgproc.polylines(outputImg, contours, true, new Scalar(0, 255, 0), 1, Imgproc.LINE_4);
            //外接矩形ROI用蓝色线条展示出来
            Imgproc.rectangle(outputImg, rect, new Scalar(255, 0, 0), 4);
        }
        return outputImg;
    }

    private void pdfProcess(File pdfFile) {
        int pNo = NumberUtils.toInt(pageNo.getText(), 0);
        int iNo = NumberUtils.toInt(imageNo.getText(), 0);
        SwingWorker<Void, PDDocument> task = new SwingWorker<Void, PDDocument>() {
            @Override
            protected Void doInBackground() throws Exception {
                PDDocument document = PDDocument.load(pdfFile);
                if (null == document) {
                    System.out.println("文件读取失败");
                    return null;
                }
                int imageNumber;
                try {
                    PDPage page = document.getPage(pNo);
                    if (null == page) {
                        System.out.printf("没有第%d页\n", pNo);
                        return null;
                    }
                    PDResources resources = page.getResources();
                    imageNumber = 0;
                    for (COSName cosName : resources.getXObjectNames()) {
                        PDXObject xObject;
                        try {
                            xObject = resources.getXObject(cosName);
                        } catch (IOException e) {
                            continue;
                        }
                        if (xObject instanceof PDImageXObject) {
                            if (imageNumber < iNo) continue;
                            PDStream stream = xObject.getStream();
                            try {
                                //先转一次流才能拿到真正的图片对象
                                PDImageXObject imageObject = new PDImageXObject(stream, resources);
                                //获得awt的BufferedImage
                                BufferedImage image = imageObject.getImage();
                                //将BufferedImage利用ImageIO.write按jpeg格式输出到ByteArrayOutputStream流中
                                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                                ImageIO.write(image, "jpeg", byteArrayOutputStream);
                                byteArrayOutputStream.flush();
                                //此时就可以得到图片的byte信息，在ByteArrayOutputStream中，然后就可以通过byte信息将图片load到Mat中
                                Mat img = Imgcodecs.imdecode(new MatOfByte(byteArrayOutputStream.toByteArray()), Imgcodecs.IMREAD_UNCHANGED);
                                if (img.empty()) continue;
                                if (img.width() < 50 || img.height() < 50) continue; //太小的图片忽略掉
                                img.convertTo(img, CvType.CV_8UC3);
                                if (img.channels() < 3) {
                                    //如果图片不是3通道的，先自己合并自己变成3通道，否则后续步骤处理报错
                                    Core.merge(Arrays.asList(img, img, img), img);
                                }
                                //寻找图片中的小图，展示切割图片的轮廓外矩形
                                Mat outputImg = splitAndDraw(img);
                                ImageUtils.fitImgSize(outputImg, IMAGE_WIDTH, IMAGE_HEIGHT);
                                MatOfByte matOfByte = new MatOfByte();
                                Imgcodecs.imencode(".jpeg", outputImg, matOfByte);
                                imageLabel.setIcon(new ImageIcon(matOfByte.toArray()));
                                break;
                            } catch (IOException ignored) {
                                ignored.printStackTrace();
                            }
                            imageNumber++;
                        }
                    }
                } finally {
                    try {
                        document.close();
                    } catch (IOException ignored) {
                    }
                }
                if (imageNumber < iNo) {
                    System.out.printf("没有第%d张图片\n", iNo);
                }
                return null;
            }
        };
        task.execute();
    }

    protected void init() {
        this.setTitle("PDF图像切图演示工具");
        //vBox放置上下分布的图像显示区域和下面的操作按钮
        Box vBox = Box.createVerticalBox();
        //上面图像显示区域，显示PDF某页某张图
        imageLabel = new JLabel();
        imageLabel.setBorder(new LineBorder(Color.BLUE, 2));
        imageLabel.setPreferredSize(new Dimension(IMAGE_WIDTH, IMAGE_HEIGHT));
        imageLabel.setHorizontalAlignment(SwingConstants.CENTER);
        JPanel imagePanel = new JPanel();
        imagePanel.setLayout(new FlowLayout());
        imagePanel.add(imageLabel);
        vBox.add(imagePanel);
        //下面放置选择文件的操作按钮
        JFileChooser fileChooser  = new JFileChooser();
        fileChooser.setDialogTitle("选择PDF文件");
        fileChooser.setMultiSelectionEnabled(false);
        fileChooser.setCurrentDirectory(new File("/Users/kwok/Downloads/"));
        fileChooser.setFileFilter(new FileFilter() {
            @Override
            public boolean accept(File f) {
                if (null == f) return false;
                String suffix = f.getName().substring(f.getName().lastIndexOf(".") + 1);
                if ("pdf".equalsIgnoreCase(suffix)) {
                    return true;
                }
                return false;
            }
            @Override
            public String getDescription() {
                return "支持pdf";
            }
        });
        JButton chooseFileBtn = new JButton("选择文件");
        chooseFileBtn.addActionListener(e -> {
            int retValue = fileChooser.showDialog(null, "打开PDF");
            if (retValue == JFileChooser.APPROVE_OPTION) {
                selectedFile = fileChooser.getSelectedFile();
                pdfProcess(selectedFile);
            } else {
                System.out.println("用户取消选中文件");
            }
        });
        JPanel menuBar = new JPanel();
        menuBar.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 5));
        menuBar.setBorder(new LineBorder(Color.RED, 2));
        menuBar.add(chooseFileBtn);
        menuBar.add(new JLabel("页数"));
        pageNo = new JTextField(5);
        menuBar.add(pageNo);
        menuBar.add(new JLabel("图数"));
        imageNo = new JTextField(5);
        menuBar.add(imageNo);
        JButton runBtn = new JButton("重新运行");
        runBtn.addActionListener(e -> {
            if (null != selectedFile && selectedFile.exists()) {
                pdfProcess(selectedFile);
            }
        });
        menuBar.add(runBtn);
        vBox.add(menuBar);
        //把vBox加入到应用容器中
        this.add(vBox);
    }

    public static void main(String[] args) {
        PdfImageSplitPreviewer previewer = new PdfImageSplitPreviewer();
        previewer.setVisible(true);
    }
}
